/*
 *     This file is part of Mobile GPS Logger.
 *
 *     Mobile GPS Logger is free software: you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation, either version 3 of the
 *     License, or (at your option) any later version.
 *
 *     Mobile GPS Logger is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License v3 for more details.
 *
 *     You should have received a copy of the GNU General Public License v3
 *     along with Mobile GPS Logger. If not, see <http://www.gnu.org/licenses/>.
 */

package cx.ath.skyflyer.gpslog;

import cx.ath.skyflyer.chart.*;
import cx.ath.skyflyer.gpslog.gps.*;
import cx.ath.skyflyer.gpslog.io.*;
import cx.ath.skyflyer.gpslog.nmea.*;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

/**
 * Test for the chart. Fills the chart with points read from a file containing
 * NMEA0183 sentences.
 * @author Ahti Legonkov
 */
public class SpeedChart
        extends Chart
        implements CommandListener
{
    public SpeedChart(MIDlet midlet) {
        super(true);

        this.prev = Display.getDisplay(midlet).getCurrent();
        this.midlet = midlet;

        this.addCommand(CMD_HELP);
        this.addCommand(CMD_BACK);
        if (Configuration.getPointsOnChartVisible()) {
            this.lineChart().showDataPoints();
            this.addCommand(CMD_POINTS_OFF);
        }
        else {
            this.lineChart().hideDataPoints();
            this.addCommand(CMD_POINTS_ON);
        }
        this.setCommandListener(this);
    }

    public SpeedChart(MIDlet midlet, String fileName) throws IOException {

        this(midlet);

        loadFile(fileName);

    }

    public void loadFile(String fileName) throws IOException {

        System.gc();
        
        final long start = System.currentTimeMillis();

        FileConnection fileConn = (FileConnection)Connector.open(fileName, Connector.READ);
        InputStreamReader fisr = new InputStreamReader(fileConn.openInputStream());
        LineReader in = new LineReader(fisr);

        GpsFixData prevFix = null;
        Position pos1 = null;

        DataSeries verticalSpeeds = new DataSeries("vertical");
        verticalSpeeds.setColor(Color.RED);
        lineChart().addSeries(verticalSpeeds);

        DataSeries horizontalSpeeds = new DataSeries("horizontal");
        horizontalSpeeds.setColor(Color.BLUE);
        lineChart().addSeries(horizontalSpeeds);

        Time firstTime = null;
        Time lastTime = null;
        DataPoint prevH = null;
        DataPoint prevV = null;
        int n = 0;
        boolean addPoints = false;
        while (fisr.ready()) {
            String line = in.readString(false);

            if (line.charAt(0) != '$')
                continue;

            try {

                GpsFixData fix = new GpsFixData(line);
                if (fix.getFixQuality() == GpsFixData.NO_FIX)
                    continue;

                ++n;

                Position pos2 = new Position(fix);

                if (prevFix != null) {

                    double alt = fix.getAltitude();
                    Time t = fix.getTime();
                    if (firstTime == null)
                        firstTime = t;
                    lastTime = t;

                    double deltaT = t.sub(prevFix.getTime());
                    double deltaAlt = prevFix.getAltitude() - alt;
                    double verticalSpeed = Double.POSITIVE_INFINITY;
                    if (deltaT > 0)
                        verticalSpeed = 3.6 * deltaAlt / deltaT; // km/h

                    // start adding points
                    if (verticalSpeed > 20) {
                        addPoints = true;
                    }

                    if (addPoints) {

                        // TODO: configure these values?
                        final double dt = 1;
                        final double RC = 2;
                        final double alpha = dt / (RC + dt);
                        
                        if (prevV != null)
                            verticalSpeed = alpha * verticalSpeed + (1-alpha) * prevV.y;
                        DataPoint dpV = new DataPoint(t.sub(firstTime), verticalSpeed);
                        verticalSpeeds.add(dpV);
                        prevV = dpV;

                        // distance in meters.
                        final double horizontalDistance = pos2.distanceFrom(pos1);

                        double horizontalSpeed = Double.POSITIVE_INFINITY;
                        if (deltaT > 0)
                            horizontalSpeed = 3.6 * horizontalDistance / deltaT;

                        if (prevH != null)
                            horizontalSpeed = alpha * horizontalSpeed + (1-alpha) * prevH.y;
                        DataPoint dpH = new DataPoint(t.sub(firstTime), horizontalSpeed);
                        horizontalSpeeds.add(dpH);
                        prevH = dpH;
                    }
                    else {
                        firstTime = t;
                    }
                }

                pos1 = pos2;
                prevFix = fix;

                continue;
            }
            catch (OutOfMemoryError ex) {
                Alert a = new Alert("Error");
                a.setString("Out of memory");
                a.setType(AlertType.ERROR);
                a.setTimeout(Alert.FOREVER);
                Display.getDisplay(midlet).setCurrent(a);
                System.out.println("Out of memory:"+ex);
                break;
            }
            catch (UnsupportedTypeException ex) { }
            catch (ChecksumMismatchException ex) { }
        }
        
        final long end = System.currentTimeMillis();

        final double timeSpan = (lastTime != null)
            ? lastTime.sub(firstTime)
            : 6.0;

        setAbsoluteMaxX(timeSpan);
        setMinMaxX(0, timeSpan);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == CMD_HELP) {
            // TODO: display help
            Alert a = new Alert("Help", "This should display help about the chart", null, AlertType.INFO);
            Display.getDisplay(midlet).setCurrent(a, this);
        }
        else if (c == CMD_BACK) {
            Display.getDisplay(midlet).setCurrent(prev);
        }
        else if (c == CMD_POINTS_ON) {
            showPoints(true);
        }
        else if (c == CMD_POINTS_OFF) {
            showPoints(false);
        }
    }

    private void showPoints(boolean show) {
        if (show) {
            this.removeCommand(CMD_POINTS_ON);
            this.addCommand(CMD_POINTS_OFF);
            this.lineChart().showDataPoints();
        }
        else {
            this.removeCommand(CMD_POINTS_OFF);
            this.addCommand(CMD_POINTS_ON);
            this.lineChart().hideDataPoints();
        }

        try {
            Configuration.setPointsOnChartVisible(show);
        }
        catch (ConfigurationException ex) {
        }

        repaint();
    }

    protected void keyPressed(int keyCode) {

        int gameAction = getGameAction(keyCode);

        switch (gameAction) {
        case UP: // zoom in
            zoomIn();
            break;

        case DOWN: // zoom out
            zoomOut();
            break;

        case LEFT: // pan left
            panLeft();
            break;

        case RIGHT: // pan right
            panRight();
            break;

        default:
            break;
        }

        repaint();
    }

    protected void keyRepeated(int keyCode) {
        keyPressed(keyCode);
    }

    protected void paint(Graphics g) {

        g.setColor(Color.WHITE.intValue());
        g.fillRect(0, 0, getWidth(), getHeight());
        super.paint(g);
    }

    private final Command CMD_HELP = new Command("Help", "Help", Command.SCREEN, 0);
    private final Command CMD_BACK = new Command("Back", "Back", Command.BACK, 0);
    private final Command CMD_POINTS_ON = new Command("Points on", "Points on", Command.SCREEN, 1);
    private final Command CMD_POINTS_OFF = new Command("Points off", "Points off", Command.SCREEN, 1);

    private final MIDlet midlet;
    private final Displayable prev;
}
